/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年5月31日
 */
package com.massyframework.beanskin.runtime.framework.installing.library;

import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.massyframework.beanskin.Module;
import com.massyframework.beanskin.ModuleIdentifier;
import com.massyframework.beanskin.ModuleJarsLocator;
import com.massyframework.beanskin.ModuleRepository;
import com.massyframework.beanskin.ModuleRuntimeException;
import com.massyframework.beanskin.RequireLibraryDescriptor;
import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.framework.Framework;
import com.massyframework.beanskin.library.Library;
import com.massyframework.beanskin.library.LibraryBuilder;
import com.massyframework.beanskin.runtime.ModuleRegistry;
import com.massyframework.beanskin.runtime.framework.installing.ModuleClassLoaderBuilderFactory;
import com.massyframework.beanskin.runtime.framework.installing.ModuleScanner;
import com.massyframework.beanskin.runtime.framework.installing.SimpleRequireLibraryIntersetor;
import com.massyframework.beanskin.service.ServiceRepositoryFactory;
import com.massyframework.beanskin.service.secure.SecureCapacity;
import com.massyframework.beanskin.util.classloading.LocalResourcesPath;
import com.massyframework.beanskin.util.classloading.ModuleClassLoader;
import com.massyframework.beanskin.util.classloading.ModuleClassLoaderBuilder;
import com.massyframework.beanskin.util.classloading.ResourceLocateFilterRepository;
import com.massyframework.beanskin.util.exceptioncollect.ExceptionCollectUtils;
import com.massyframework.beanskin.util.exceptioncollect.ExceptionGroup;
import com.massyframework.beanskin.util.localstore.LocalStore;
import com.massyframework.beanskin.util.sorting.graph.Edge;
import com.massyframework.beanskin.util.sorting.graph.Graph;
import com.massyframework.beanskin.util.sorting.graph.GraphFactory;
import com.massyframework.beanskin.util.sorting.graph.Vertex;

/**
 * 类库安装器
 * @author huangkh
 *
 */
public class LibraryInstaller {

	private Logger logger = LoggerFactory.getLogger(LibraryInstaller.class);

	
	/**
	 * 加载并安装
	 * @param kernel {@link Assembly},装配件
	 * @param localStore {@link LocalStore},本地存储
	 */
	public List<LibraryBuilder> loadingLibraries(Assembly assembly, LocalStore localStore) {
		Path[] roots = this.getRoots(localStore);
		Map<ModuleIdentifier, LibraryBuilder> libraryMap = this.loadLibrarys(assembly, localStore, roots);
		
		//创建有向无环图，梳理类库之间的依赖关系
		Graph<LibraryBuilder> graph = GraphFactory.createGraph(LibraryBuilder.class);
		List<LibraryBuilder> result =  new ArrayList<>(libraryMap.values());
		libraryMap.values().forEach( builder-> graph.insert(builder));
		
		LibraryBuilderComparator comparator = new LibraryBuilderComparator();
		int size = result.size();
		for (int i=0; i<size; i++) {
			for (int j=1; j<size; j++) {
				LibraryBuilder builder1 = result.get(i);
				LibraryBuilder builder2 = result.get(j);
								
				try {
					int order = comparator.compare(builder1, builder2);
					switch (order) {
					case -1:{
						graph.insert(builder1, builder2, new Edge(1, null));
						break;
					}case 1: {
						graph.insert(builder2, builder1, new Edge(1, null));
						break;
					}
				}
				}catch(Exception e) {
					graph.remove(builder1);
					graph.remove(builder2);
					ExceptionCollectUtils.addException(
							new ModuleRuntimeException(builder1.getModuleIdentifier(), e));
				}
			}
		}
		
		result.clear();
		Predicate<Vertex<LibraryBuilder>> filter = vertex -> vertex.getInDegree() == 0;
		List<LibraryBuilder> tmp = graph.remove(filter);
		while (!tmp.isEmpty()) {
			result.addAll(tmp);
			tmp = graph.remove( filter);
		}

		if (graph.vertexNumber() != 0) {
			filter = vertex -> {return true; };
			tmp = graph.remove( filter);
			
			//生成例外
			for (LibraryBuilder builder: tmp) {
				List<RequireLibraryDescriptor> undepends = new ArrayList<RequireLibraryDescriptor>();
				for (RequireLibraryDescriptor descriptor: builder.getRequires()) {
					ModuleIdentifier module = libraryMap.keySet().stream()
						.filter( identifier -> 
							descriptor.getGroupId().equals(identifier.getGroupId()) &&
							descriptor.getModuleId().equals(identifier.getModuleId()) &&
							descriptor.getVersionRange().includes(identifier.getVersion())
						).findFirst().orElse(null);
					if (module == null) {
						undepends.add(descriptor);
					}
				}
				StringBuilder sb = new StringBuilder();
				sb.append("library[")
					.append(builder.getModuleIdentifier().toIdentifier())
					.append("] cannot be install. not resolve: ");
				for (int i=0; i<undepends.size(); i++) {
					RequireLibraryDescriptor desc = undepends.get(i);
					sb.append(desc.toString());
					if (i != undepends.size() -1) {
						sb.append(",");
					}
				}
				String message = sb.toString();
				if (logger.isErrorEnabled()) {
					logger.error(message);
				}
				ExceptionCollectUtils
					.addException(new ModuleRuntimeException(builder.getModuleIdentifier(), message));
			}
			
		}
		
		return result;
	}
	
	/**
	 * 安装类库
	 * @param builders
	 */
	public void installLibrarys(List<LibraryBuilder> builders, ModuleJarsLocator moduleJarsLocator, 
			ModuleRepository moduleRepository, Assembly kernel, ServiceRepositoryFactory serviceRepositoryFactory) {
		ModuleRegistry moduleRegistry = (ModuleRegistry) moduleRepository;
				
		for (LibraryBuilder builder: builders) {
			//jar包
			List<URL> urls = moduleJarsLocator.locate(builder.getModuleIdentifier());
			
			SimpleRequireLibraryIntersetor intersetor = new SimpleRequireLibraryIntersetor();
			List<RequireLibraryDescriptor> list = intersetor.intersection(builder.getModuleIdentifier(), builder.getRequires(), moduleRepository);
			
			List<LocalResourcesPath> paths = new ArrayList<>();
			for (RequireLibraryDescriptor require: list) {
	            Module<?> module = moduleRepository.findModuleWithOptimal(require);
	            if (module != null) {
	                LocalResourcesPath lrp = module.getModuleClassLoader().getLocalResourcePath();
	                if (lrp != null) {
	                	paths.add(lrp);
	                }
	            }
	        }
			
			SecureCapacity support = (SecureCapacity)builder; 
			ResourceLocateFilterRepository filterRepo =
					kernel.getAssemblyContext().getBean(ResourceLocateFilterRepository.class.getName(), 
							ResourceLocateFilterRepository.class);
			
			ModuleClassLoaderBuilder loaderBuilder =
					ModuleClassLoaderBuilderFactory.createModuleClassLoaderBuilder(kernel);
			ModuleClassLoader classLoader =
					loaderBuilder.publicKey(support.getPublicKey())
						.decryptClassNames(support.getDecryptClassNames())
						.decryptScriptFileNames(support.getDescryptScriptFileNames())
						.certificate(support.getX509Name(), support.getX509Encrypted())
						.parentClassLoader(Framework.class.getClassLoader())
						.moduleIdentifier(builder.getModuleIdentifier())
						.addURLs(urls)
						.addLocalResourcesPaths(paths)
						.resourceLocateFilterRepository(filterRepo)
						.build();
			
			Library library = builder.moduleRepository(moduleRepository)
					.classLoader(classLoader)
					.serviceRepositoryFactory(serviceRepositoryFactory)
					.build();
			moduleRegistry.register(library);
		}
	}
	
	/**
	 * 加载类库
	 * @param roots {@link Paths}数组
	 * @param localStore {@link LocalStore},本地存储
	 * @param exceptions {@link ExceptionGroup},例外组
	 * @return {@link Map}
	 */
	protected Map<ModuleIdentifier, LibraryBuilder> loadLibrarys(Assembly kernel, LocalStore localStore,  Path[] roots){
		ModuleScanner scanner = new ModuleScanner(roots);
		
		return scanner.scanning("library.xml", new LibraryXmlParser(kernel, localStore));
	}
	
	/**
	 * 扫描的根目录
	 * @param localStore {@link LocalStore}
	 * @return {@link Path}数组.
	 */
	protected Path[] getRoots(LocalStore localStore) {
		return new Path[] {localStore.getDeploymentDirectory(null)};
	}

}
